NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

marketsvc.py (32237B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\services\marketsvc.py
      2 import functools
      3 import itertools
      4 import logging
      5 import blue
      6 import evetypes
      7 import localization
      8 import marketutil
      9 from carbon.common.script.sys.service import Service
     10 from carbon.common.script.util.mathCommon import FloatCloseEnough
     11 from eve.common.lib import appConst as const
     12 from eve.common.script.sys import idCheckers, rowset as dbutil
     13 from eve.common.script.sys.idCheckers import IsStation
     14 from eveexceptions import UserError
     15 from expiringdict import ExpiringDict
     16 from featuremanagement.client.flag import flag_float
     17 from inventorycommon.const import typePlex
     18 from marketutil.brokerFee import BrokerFeeProvider
     19 from marketutil.const import MAX_ORDER_PRICE, MKT_TRANSACTION_TAX_DEFAULT, MKT_TRANSACTION_TAX_KEY
     20 from marketutil.feature_flags.client import is_global_plex_market_experiment_enabled
     21 from marketutil.skilllimits import GetMarketSkills
     22 from npcs.npccorporations import get_corporation_faction_id
     23 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     24 from playerprogression.marketprogressionnotifier import MarketProgressionNotifier
     25 import structures
     26 from caching.memoize import Memoize
     27 logger = logging.getLogger(__name__)
     28 CACHE_INTERVAL = 300
     29 
     30 def SortAsks(x, y, headers):
     31     priceIdx = headers.index('price')
     32     if x[priceIdx] < y[priceIdx]:
     33         return -1
     34     elif x[priceIdx] > y[priceIdx]:
     35         return 1
     36     issuedIdx = headers.index('issueDate')
     37     if x[issuedIdx] < y[issuedIdx]:
     38         return -1
     39     elif x[issuedIdx] > y[issuedIdx]:
     40         return 1
     41     jmpIdx = headers.index('jumps')
     42     if x[jmpIdx] < y[jmpIdx]:
     43         return -1
     44     elif x[jmpIdx] > y[jmpIdx]:
     45         return 1
     46     orderIdx = headers.index('orderID')
     47     if x[orderIdx] < y[orderIdx]:
     48         return -1
     49     else:
     50         return 1
     51 
     52 
     53 def SortBids(x, y, headers):
     54     priceIdx = headers.index('price')
     55     if x[priceIdx] < y[priceIdx]:
     56         return 1
     57     elif x[priceIdx] > y[priceIdx]:
     58         return -1
     59     issuedIdx = headers.index('issueDate')
     60     if x[issuedIdx] < y[issuedIdx]:
     61         return -1
     62     elif x[issuedIdx] > y[issuedIdx]:
     63         return 1
     64     jmpIdx = headers.index('jumps')
     65     rngIdx = headers.index('range')
     66     if x[jmpIdx] - max(0, x[rngIdx]) < y[jmpIdx] - max(0, y[rngIdx]):
     67         return -1
     68     elif x[jmpIdx] - x[rngIdx] > y[jmpIdx] - y[rngIdx]:
     69         return 1
     70     orderIdx = headers.index('orderID')
     71     if x[orderIdx] < y[orderIdx]:
     72         return -1
     73     else:
     74         return 1
     75 
     76 
     77 class MarketQuote(Service):
     78     __guid__ = 'svc.marketQuote'
     79     __servicename__ = 'marketquote'
     80     __displayname__ = 'Market Quote Service'
     81     __notifyevents__ = ['OnSessionChanged',
     82      'OnOwnOrdersChanged',
     83      'OnMarketItemReceived',
     84      'OnSellRepackageErrors']
     85     __dependencies__ = ['clientPathfinderService']
     86     __update_on_reload__ = 1
     87 
     88     def Run(self, *args, **kwargs):
     89         self.groupsQuotes = {}
     90         self.lastGotGroupsQuotes = {}
     91         self.lastGotGroupQuotes = {}
     92         self.groupQuotes = {}
     93         self.waitingFor = {}
     94         self.locks = {}
     95         self.orderCache = {}
     96         market_tax_getter = flag_float(key=MKT_TRANSACTION_TAX_KEY, default_value=MKT_TRANSACTION_TAX_DEFAULT, callback=self._OnMktTransactionTaxChanged)
     97         self._market_tax = market_tax_getter()
     98         sm.FavourMe(self.OnOwnOrdersChanged)
     99         self.historiesForTypeIDsCache = ExpiringDict(1000, 10800)
    100         self.SetupMarketProgressionNotifier()
    101 
    102     def SetupMarketProgressionNotifier(self):
    103         self.marketProgressionNotifier = MarketProgressionNotifier(get_market_escrow_function=self.GetMyMarketEscrow)
    104         self.SubscribeMarketProgressWatcher = self.marketProgressionNotifier.register_subscriber
    105         self.UnsubscribeMarketProgressWatcher = self.marketProgressionNotifier.unregister_subscriber
    106 
    107     def OnOwnOrdersChanged(self, orders, reason, isCorp):
    108         sm.services['objectCaching'].InvalidateCachedMethodCall('marketProxy', 'GetCharOrders')
    109         if orders is not None:
    110             for order in orders:
    111                 sm.services['objectCaching'].InvalidateCachedMethodCall('marketProxy', 'GetOrders', order.typeID)
    112                 sm.services['objectCaching'].InvalidateCachedMethodCall('marketProxy', 'GetPlexOrders')
    113 
    114         sm.services['objectCaching'].InvalidateCachedMethodCall('marketProxy', 'GetSystemAsks')
    115         sm.services['objectCaching'].InvalidateCachedMethodCall('marketProxy', 'GetStationAsks')
    116         sm.services['objectCaching'].InvalidateCachedMethodCall('marketProxy', 'GetMarketOrderHistory')
    117         sm.services['objectCaching'].InvalidateCachedMethodCall('marketProxy', 'GetPlexBest')
    118         sm.ScatterEvent('OnMarketEscrowChanged')
    119         if self._IsAtLeastOneNonImmediate(orders):
    120             sm.GetService('neocom').Blink('marketOrders', localization.GetByLabel('UI/Neocom/Blink/MarketOrdersChanged'))
    121 
    122     @staticmethod
    123     def _IsAtLeastOneNonImmediate(orders):
    124         return any([ order.duration != 0 for order in orders ])
    125 
    126     def OnSessionChanged(self, isRemote, session, change):
    127         if 'regionid' in change:
    128             self.LogInfo('MarketSvc: Region change, clearing internal cache')
    129             self.ClearAll()
    130         if 'charid' in change:
    131             self.GetMarketTaxAtStructure.clear_memoized()
    132 
    133     def OnMarketItemReceived(self, itemID, typeID, flag, locationID):
    134         sm.ScatterEvent('OnClientEvent_MarketItemReceived', itemID, typeID, flag, locationID)
    135 
    136     def OnSellRepackageErrors(self, repackageErrorCount):
    137         if len(repackageErrorCount) == 1 and 'CantRepackageDamagedItem' in repackageErrorCount:
    138             itemCount = repackageErrorCount['CantRepackageDamagedItem']
    139             prompt_player_fsd_dialog('CantRepackageMultipleDamagedItems', {'numItems': itemCount})
    140         else:
    141             prompt_player_fsd_dialog('CantRepackageMultipleItems')
    142 
    143     def GetBrokersFeeInfo(self, stationID, oldAmount, newAmount, commissionPercentage, modificationFeeDiscount):
    144         brokerFeeProvider = self._GetBrokerFeeProviderFromStationID(stationID)
    145         return brokerFeeProvider.GetBrokerFeeInfo(oldAmount, newAmount, commissionPercentage, modificationFeeDiscount)
    146 
    147     def GetSccSurchargeFeeInfo(self, stationID, oldAmount, newAmount, modificationFeeDiscount = None):
    148         brokerFeeProvider = self._GetBrokerFeeProviderFromStationID(stationID)
    149         return brokerFeeProvider.GetSccSurchargeInfo(oldAmount, newAmount, modificationFeeDiscount)
    150 
    151     def GetBrokersFeeCommissionFromStationID(self, stationID):
    152         quoteSvc = sm.GetService('marketQuote')
    153         limits = quoteSvc.GetSkillLimits(stationID)
    154         feePercentageAtLocation = limits.GetBrokersFeeForLocation(stationID)
    155         if not feePercentageAtLocation:
    156             return 0.0
    157         brokerFeeProvider = self._GetBrokerFeeProviderFromStationID(stationID)
    158         return brokerFeeProvider.GetAdjustedCommissionPercentage(feePercentageAtLocation)
    159 
    160     def _GetBrokerFeeProviderFromStationID(self, stationID):
    161         station = sm.GetService('ui').GetStation(stationID)
    162         stationOwnerID = None
    163         solarSystemID = None
    164         if station:
    165             stationOwnerID = station.ownerID
    166             solarSystemID = station.solarSystemID
    167             sccSurchargeTax = 0
    168         else:
    169             structureInfo = sm.GetService('structureDirectory').GetStructureInfo(stationID)
    170             if structureInfo:
    171                 solarSystemID = structureInfo.solarSystemID
    172             sccSurchargeTax = const.marketSccCommissionPercentage
    173         factionToCharStanding, corpToCharStanding = self.FactionAndCorpToCharStandings(stationOwnerID, session.charid)
    174         brokerFeeProvider = BrokerFeeProvider(charWarfactionID=session.warfactionid, solarSystemID=solarSystemID, factionToCharStanding=factionToCharStanding, corpToCharStanding=corpToCharStanding, sccSurchargeTax=sccSurchargeTax)
    175         return brokerFeeProvider
    176 
    177     def CanTradeAtStation(self, bid, stationID, typeID, retList = None):
    178         if typeID == typePlex and is_global_plex_market_experiment_enabled():
    179             return True
    180         limits = self.GetSkillLimits(stationID)
    181         if bid:
    182             jumps = self.GetStationDistance(stationID)
    183             if retList is not None:
    184                 retList.append(jumps)
    185                 retList.append(limits['bid'])
    186             if jumps <= limits['bid']:
    187                 return True
    188         else:
    189             jumps = self.GetStationDistance(stationID)
    190             if retList is not None:
    191                 retList.append(jumps)
    192                 retList.append(limits['ask'])
    193             if jumps <= limits['ask']:
    194                 return True
    195         return False
    196 
    197     def GetStationDistance(self, stationID, getFastestRoute = True):
    198         if stationID in (session.stationid, session.structureid):
    199             return -1
    200         solarSystemID = self.GetSolarSystemForMarketLocation(stationID)
    201         regionID = sm.GetService('map').GetRegionForSolarSystem(solarSystemID)
    202         if regionID != session.regionid:
    203             return const.rangeRegion
    204         if getFastestRoute:
    205             jumps = self.clientPathfinderService.GetJumpCountFromCurrent(solarSystemID)
    206         else:
    207             jumps = self.clientPathfinderService.GetAutopilotJumpCount(session.solarsystemid2, solarSystemID)
    208         if jumps >= 1:
    209             return jumps
    210         return 0
    211 
    212     def GetRegionBest(self):
    213         return self.GetMarketProxy().GetRegionBest()
    214 
    215     def GetPlexBest(self):
    216         return self.GetMarketProxy().GetPlexBest()
    217 
    218     def GetBaseTax(self, stationID):
    219         if IsStation(stationID):
    220             return const.marketCommissionPercentage
    221         else:
    222             return self.GetMarketTaxAtStructure(stationID)
    223 
    224     @Memoize(2)
    225     def GetMarketTaxAtStructure(self, structureID):
    226         return sm.RemoteSvc('structureSettings').CharacterGetService(structureID, structures.SERVICE_MARKET)
    227 
    228     def GetSkillLimits(self, stationID):
    229         return marketutil.GetSkillLimits(lambda typeIDs: self.GetMarketSkills(), self.GetBaseTax, IsStation(stationID), self._market_tax, session.warfactionid, session.solarsystemid2)
    230 
    231     def _OnMktTransactionTaxChanged(self, evaluate_flag):
    232         self._market_tax = evaluate_flag()
    233 
    234     @staticmethod
    235     def GetMarketSkills():
    236         return GetMarketSkills(sm.GetService('skills').MyEffectiveSkillLevelsByID())
    237 
    238     @staticmethod
    239     def GetMarketProxy():
    240         return sm.ProxySvc('marketProxy')
    241 
    242     def BuyStuff(self, stationID, typeID, price, quantity, orderRange = None, minVolume = 1, duration = 0, useCorp = False, expectedBrokersFee = None):
    243         if orderRange is None:
    244             orderRange = const.rangeStation
    245         price = round(price, 2)
    246         if price > MAX_ORDER_PRICE:
    247             raise ValueError('Price can not exceed %s', MAX_ORDER_PRICE)
    248         if quantity < 0:
    249             raise UserError('RepackageBeforeSelling', {'item': typeID})
    250         if minVolume > quantity:
    251             raise UserError('MktInvalidMinVolumeCannotExceedQuantity')
    252         self.GetMarketProxy().PlaceBuyOrder(int(stationID), int(typeID), round(float(price), 2), int(quantity), int(orderRange), int(minVolume), int(duration), useCorp, expectedBrokersFee)
    253         sm.ScatterEvent('OnClientEvent_OrderPlaced', [typeID])
    254 
    255     def BuyMulti(self, stationID, itemList, useCorp):
    256         ordersCreated = self.GetMarketProxy().BuyMultipleItems(stationID, itemList, useCorp)
    257         sm.ScatterEvent('OnClientEvent_OrderPlaced', [ order.typeID for order in ordersCreated ])
    258         return ordersCreated
    259 
    260     def SellMulti(self, itemList, useCorp, duration, expectedBrokersFee):
    261         return self.GetMarketProxy().PlaceMultiSellOrder(itemList, useCorp, duration, expectedBrokersFee)
    262 
    263     def SellPlex(self, item, useCorp, duration, expectedBrokersFee):
    264         return self.GetMarketProxy().PlacePlexSellOrder(item, useCorp, duration, expectedBrokersFee)
    265 
    266     def CancelOrder(self, orderID, regionID):
    267         self.GetMarketProxy().CancelCharOrder(orderID, regionID)
    268 
    269     def ModifyOrder(self, order, newPrice):
    270         if newPrice > MAX_ORDER_PRICE:
    271             raise ValueError('Price can not exceed %s', MAX_ORDER_PRICE)
    272         self.GetMarketProxy().ModifyCharOrder(order.orderID, newPrice, order.bid, order.stationID, order.solarSystemID, order.price, order.range, order.volRemaining, order.issueDate)
    273 
    274     def ModifyPlexOrder(self, order, newPrice):
    275         if newPrice > MAX_ORDER_PRICE:
    276             raise ValueError('Price can not exceed %s', MAX_ORDER_PRICE)
    277         self.GetMarketProxy().ModifyPlexCharOrder(order.orderID, newPrice, order.bid, order.stationID, order.solarSystemID, order.price, order.range, order.volRemaining, order.issueDate)
    278 
    279     def GetStationAsks(self):
    280         if session.stationid is None and session.structureid is None:
    281             raise AttributeError('Must be in station')
    282         return self.GetMarketProxy().GetStationAsks()
    283 
    284     def GetSystemAsks(self):
    285         if session.solarsystemid2 is None:
    286             raise AttributeError('Must be in a solarsystem')
    287         return self.GetMarketProxy().GetSystemAsks()
    288 
    289     def PopulatePriceHistoryCacheForManyTypeIDs(self, typeIDs):
    290         missingTypeIDs = set()
    291         for eachTypeID in typeIDs:
    292             h = self.historiesForTypeIDsCache.get(eachTypeID, None)
    293             if h is None:
    294                 missingTypeIDs.add(eachTypeID)
    295 
    296         needPlexHistory = False
    297         if typePlex in missingTypeIDs:
    298             needPlexHistory = True
    299             missingTypeIDs.discard(typePlex)
    300         if missingTypeIDs:
    301             historiesByTypeIDs = self.GetMarketProxy().GetHistoryForManyTypeIDs(missingTypeIDs)
    302             self.historiesForTypeIDsCache.update(historiesByTypeIDs)
    303         if needPlexHistory:
    304             plexHistories = self.GetMarketProxy().GetPlexHistory()
    305             self.historiesForTypeIDsCache.update(plexHistories)
    306 
    307     def GetPriceHistoryFromCache(self, typeID):
    308         if typeID in self.historiesForTypeIDsCache:
    309             old, new = self.historiesForTypeIDsCache[typeID]
    310             return self.GetHistoryRowList(old, new)
    311         self.LogWarn('GetPriceHistoryFromCache:Missing price history for typeID=', typeID)
    312         return self.GetPriceHistory(typeID)
    313 
    314     def GetPriceHistory(self, typeID):
    315         if typeID == typePlex:
    316             old = self.GetMarketProxy().GetPlexOldPriceHistory()
    317             new = self.GetMarketProxy().GetPlexNewPriceHistory()
    318         else:
    319             old = self.GetMarketProxy().GetOldPriceHistory(typeID)
    320             new = self.GetMarketProxy().GetNewPriceHistory(typeID)
    321         history = self.GetHistoryRowList(old, new)
    322         return history
    323 
    324     @staticmethod
    325     def GetHistoryRowList(old, new):
    326         history = dbutil.RowList((new.header, []))
    327         lastTime = blue.os.GetWallclockTimeNow()
    328         midnightToday = lastTime / const.DAY * const.DAY
    329         lastPrice = 0.0
    330         for entry in old:
    331             while lastTime + const.DAY < entry.historyDate:
    332                 lastTime += const.DAY
    333                 history.append(blue.DBRow(history.header, [lastTime,
    334                  lastPrice,
    335                  lastPrice,
    336                  lastPrice,
    337                  0,
    338                  0]))
    339 
    340             history.append(entry)
    341             lastTime = entry.historyDate
    342             lastPrice = entry.avgPrice
    343 
    344         while lastTime < midnightToday - const.DAY:
    345             lastTime += const.DAY
    346             history.append(blue.DBRow(history.header, [lastTime,
    347              lastPrice,
    348              lastPrice,
    349              lastPrice,
    350              0,
    351              0]))
    352 
    353         if len(new):
    354             history.extend(new)
    355         else:
    356             history.append(blue.DBRow(history.header, [blue.os.GetWallclockTimeNow(),
    357              lastPrice,
    358              lastPrice,
    359              lastPrice,
    360              0,
    361              0]))
    362         return history
    363 
    364     def GetAveragePrice(self, typeID, days = 7):
    365         history = self.GetPriceHistory(typeID)
    366         now = blue.os.GetWallclockTime()
    367         averagePrice = -1.0
    368         volume = 0
    369         priceVolume = 0.0
    370         for entry in history:
    371             if entry[0] < now - days * const.DAY:
    372                 continue
    373             priceVolume += entry[3] * entry[4]
    374             volume += entry[4]
    375 
    376         if volume > 0:
    377             averagePrice = priceVolume / volume
    378         else:
    379             averagePrice = float(evetypes.GetBasePrice(typeID)) / evetypes.GetPortionSize(typeID)
    380             if averagePrice <= 0.0:
    381                 averagePrice = 1.0
    382         return round(float(averagePrice), 2)
    383 
    384     def GetMyOrders(self):
    385         return self.GetMarketProxy().GetCharOrders()
    386 
    387     def GetCorporationOrders(self):
    388         return self.GetMarketProxy().GetCorporationOrders()
    389 
    390     def GetMarketOrderHistory(self):
    391         return self.GetMarketProxy().GetMarketOrderHistory()
    392 
    393     def GetMyMarketEscrow(self):
    394         iskEscrow = 0.0
    395         itemsEscrow = 0.0
    396         try:
    397             marketEscrow = self.GetMarketProxy().GetCharEscrow()
    398             iskEscrow = float(marketEscrow.iskEscrow or 0)
    399             itemsEscrow = float(marketEscrow.itemsEscrow or 0)
    400         except Exception as exception:
    401             msg = 'Failed to calculate total market escrow, cause: %s' % getattr(exception, 'msg', str(exception))
    402             self.LogException(msg)
    403 
    404         return (iskEscrow, itemsEscrow)
    405 
    406     def GetBestBidWithStationID(self, item, locationID):
    407         stationID = sm.GetService('invCache').GetStationIDOfItem(item)
    408         return self.GetBestBid(item.typeID, locationID, stationID)
    409 
    410     def GetBestBid(self, typeID, locationID, stationID = None):
    411         self.RefreshOrderCache(typeID)
    412         self.RefreshJumps(typeID, locationID=locationID)
    413         bids = self.orderCache[typeID][1]
    414         for bid in bids:
    415             if self.SkipBidDueToStructureRestrictions(bid, stationID):
    416                 continue
    417             return bid
    418 
    419     def GetSellCountEstimate(self, typeID, stationID, price, maxToSell):
    420         estimate = 0
    421         leftToSell = maxToSell
    422         bids = self.GetMatchableBids(typeID, stationID, maxToSell)
    423         for b in bids:
    424             if b.price >= price and min(b.minVolume, b.volRemaining) <= leftToSell:
    425                 estimate += b.volRemaining
    426                 leftToSell -= b.volRemaining
    427             if estimate > maxToSell:
    428                 break
    429 
    430         return min(estimate, maxToSell)
    431 
    432     def GetMatchableBids(self, typeID, stationID, amount, solarSystemID = None):
    433         if solarSystemID is None:
    434             solarSystemID = self.GetSolarSystemForMarketLocation(stationID)
    435         self.RefreshOrderCache(typeID)
    436         self.RefreshJumps(typeID, locationID=solarSystemID)
    437         bids = self.orderCache[typeID][1]
    438         if len(bids) == 0:
    439             return bids
    440         candidates = dbutil.RowList([], bids.columns)
    441         jmpIdx = bids.columns.index('jumps')
    442         rngIdx = bids.columns.index('range')
    443         minIdx = bids.columns.index('minVolume')
    444         volIdx = bids.columns.index('volRemaining')
    445         staIdx = bids.columns.index('stationID')
    446         for x in bids:
    447             bidJumps = x[jmpIdx]
    448             bidRange = x[rngIdx]
    449             bidStationID = x[staIdx]
    450             if typeID == typePlex and is_global_plex_market_experiment_enabled():
    451                 bidIsInRange = True
    452             else:
    453                 bidIsInRange = bidJumps <= bidRange or bidRange == const.rangeStation and bidStationID == stationID
    454             bidMinAmount = x[minIdx]
    455             bidVolRemaining = x[volIdx]
    456             bidAmountIsOk = bidMinAmount <= amount or bidVolRemaining < bidMinAmount and amount >= bidVolRemaining
    457             isGoodCandidate = bidIsInRange and bidAmountIsOk
    458             if isGoodCandidate and not self.SkipBidDueToStructureRestrictions(x, stationID):
    459                 candidates.append(x)
    460 
    461         return candidates
    462 
    463     @staticmethod
    464     def SkipBidDueToStructureRestrictions(bid, itemStationID):
    465         if idCheckers.IsStation(itemStationID) and idCheckers.IsStation(bid.stationID):
    466             return False
    467         if bid.typeID == typePlex and is_global_plex_market_experiment_enabled():
    468             return False
    469         inStructure = itemStationID and not idCheckers.IsStation(itemStationID)
    470         if inStructure and bid.stationID != itemStationID:
    471             return True
    472         return False
    473 
    474     @staticmethod
    475     def GetSolarSystemForMarketLocation(stationID):
    476         if idCheckers.IsStation(stationID):
    477             station = sm.GetService('ui').GetStationStaticInfo(stationID)
    478             return station.solarSystemID
    479         else:
    480             structureInfo = sm.GetService('structureDirectory').GetStructureInfo(stationID)
    481             return structureInfo.solarSystemID
    482 
    483     def GetBestMatchableBid(self, typeID, stationID, amount = 1, solarSystemID = None):
    484         bids = self.GetMatchableBids(typeID, stationID, amount, solarSystemID=solarSystemID)
    485         try:
    486             return bids[0]
    487         except IndexError:
    488             return None
    489 
    490     def GetBestPrice(self, typeID, item, stacksize, solarSystemID):
    491         stationID = sm.GetService('invCache').GetStationIDOfItem(item)
    492         bestMatchableBid = self.GetBestMatchableBid(typeID, stationID, stacksize, solarSystemID=solarSystemID)
    493         if bestMatchableBid:
    494             return bestMatchableBid.price
    495         return self.GetAveragePrice(typeID)
    496 
    497     def GetBestAverageAsk(self, typeID, bidRange = None, amount = 1, locationID = None):
    498         self.RefreshOrderCache(typeID)
    499         self.RefreshJumps(typeID, locationID=locationID)
    500         if bidRange is None:
    501             bidRange = const.rangeRegion
    502         self.LogInfo('[GetBestAverageAsk]', 'typeID:', typeID, 'range:', bidRange, 'amount:', amount)
    503         self.RefreshOrderCache(typeID)
    504         saveAmount = amount
    505         amount = 1
    506         asks = self.orderCache[typeID][0]
    507         if len(asks) == 0:
    508             return
    509         candidates = {}
    510         if bidRange == const.rangeRegion:
    511             for x in asks:
    512                 if x.volRemaining >= amount:
    513                     if x.stationID in candidates:
    514                         candidates[x.stationID].append(x)
    515                     else:
    516                         candidates[x.stationID] = [x]
    517 
    518         elif bidRange == const.rangeConstellation:
    519             for x in asks:
    520                 if x.volRemaining >= amount and x.constellationID == session.constellationid:
    521                     if x.stationID in candidates:
    522                         candidates[x.stationID].append(x)
    523                     else:
    524                         candidates[x.stationID] = [x]
    525 
    526         elif bidRange == const.rangeSolarSystem or session.stationid is None:
    527             for x in asks:
    528                 if x.volRemaining >= amount and x.solarSystemID == session.solarsystemid2:
    529                     if x.stationID in candidates:
    530                         candidates[x.stationID].append(x)
    531                     else:
    532                         candidates[x.stationID] = [x]
    533 
    534         else:
    535             for x in asks:
    536                 if x.volRemaining >= amount and x.stationID == session.stationid:
    537                     if x.stationID in candidates:
    538                         candidates[x.stationID].append(x)
    539                     else:
    540                         candidates[x.stationID] = [x]
    541 
    542         resultList = []
    543         amount = saveAmount
    544         for stationID, askList in candidates.iteritems():
    545             saveAmount = amount
    546             averagePrice = 0.0
    547             cumulatedVolume = 0
    548             solarsystemID = None
    549             for ask in askList:
    550                 solarsystemID = ask.solarSystemID
    551                 available = min(ask.volRemaining, saveAmount)
    552                 cumulatedVolume += available
    553                 saveAmount -= available
    554                 averagePrice += available * ask.price
    555                 if saveAmount == 0:
    556                     break
    557 
    558             if cumulatedVolume < amount:
    559                 continue
    560             averagePrice = averagePrice / cumulatedVolume
    561             resultList.append([averagePrice, stationID, solarsystemID])
    562 
    563         if len(resultList) == 0:
    564             return
    565         resultList.sort()
    566         return resultList[0]
    567 
    568     def GetBestAskInRange(self, typeID, stationID, bidRange = None, amount = 1):
    569         if bidRange is None:
    570             bidRange = const.rangeRegion
    571         self.LogInfo('[GetBestAskInRange]', 'typeID:', typeID, 'range:', bidRange, 'amount:', amount)
    572         solarSystemID = self.GetSolarSystemForMarketLocation(stationID)
    573         self.RefreshOrderCache(typeID)
    574         self.RefreshJumps(typeID, locationID=solarSystemID)
    575         asks = self.orderCache[typeID][0]
    576         for x in asks:
    577             if x.jumps == 0 and x.stationID == stationID:
    578                 jumps = -1
    579             else:
    580                 jumps = x.jumps
    581             if jumps <= bidRange and x.volRemaining >= amount:
    582                 return x
    583 
    584     def GetBestAskPriceInStationAndNumberOrders(self, typeID, stationID, amount = 1):
    585         self.RefreshOrderCache(typeID)
    586         asks = self.orderCache[typeID][0]
    587         asksInStation = filter(lambda x: x.stationID == stationID, asks)
    588         asksInStation = sorted(asksInStation, key=lambda x: x.price)
    589         totalQty = 0
    590         numOrders = 0
    591         for eachOrder in asksInStation:
    592             totalQty += eachOrder.volRemaining
    593             numOrders += 1
    594             if totalQty >= amount:
    595                 return (eachOrder.price, numOrders)
    596 
    597         return (None, None)
    598 
    599     def GetBestBidInRange(self, typeID, askRange = None, locationID = None, amount = 1):
    600         if askRange is None:
    601             askRange = const.rangeRegion
    602         if locationID is None:
    603             if askRange == const.rangeStation:
    604                 locationID = session.stationid
    605                 if locationID is None:
    606                     return
    607             elif askRange == const.rangeSolarSystem:
    608                 locationID = session.solarsystemid2
    609             elif askRange == const.rangeConstellation:
    610                 locationID = session.constellationid
    611         self.LogInfo('[GetBestBidInRange]', 'typeID:', typeID, 'locationID:', locationID, 'range:', askRange, 'amount:', amount)
    612         self.RefreshOrderCache(typeID)
    613         bids = self.orderCache[typeID][1]
    614         if len(bids) == 0:
    615             return
    616         stationID = None
    617         solarsystemID = None
    618         constellationID = None
    619         if askRange == const.rangeStation:
    620             stationID = locationID
    621             solarsystemID = sm.RemoteSvc('map').GetStationInfo().Index('stationID')[stationID].solarSystemID
    622             constellationID = sm.GetService('map').GetItem(solarsystemID).locationID
    623         elif askRange == const.rangeSolarSystem:
    624             solarsystemID = locationID
    625             constellationID = sm.GetService('map').GetItem(solarsystemID).locationID
    626         elif askRange == const.rangeConstellation:
    627             constellationID = locationID
    628         stationIdx = bids.columns.index('stationID')
    629         rangeIdx = bids.columns.index('range')
    630         solIdx = bids.columns.index('solarSystemID')
    631         conIdx = bids.columns.index('constellationID')
    632         minIdx = bids.columns.index('minVolume')
    633         found = 0
    634         i = 0
    635         for x in bids:
    636             bidRange = x[rangeIdx]
    637             if askRange == const.rangeStation and amount >= x[minIdx] and (x[stationIdx] == stationID or bidRange == const.rangeSolarSystem and x[solIdx] == solarsystemID or bidRange == const.rangeConstellation and x[conIdx] == constellationID or bidRange == const.rangeRegion):
    638                 found = 1
    639                 break
    640             elif askRange == const.rangeSolarSystem and amount >= x[minIdx] and (bidRange in [const.rangeStation, const.rangeSolarSystem] and x[solIdx] == solarsystemID or bidRange == const.rangeConstellation and x[conIdx] == constellationID or bidRange == const.rangeRegion):
    641                 found = 1
    642                 break
    643             elif askRange == const.rangeConstellation and amount >= x[minIdx] and (bidRange in [const.rangeStation, const.rangeSolarSystem, const.rangeConstellation] and x[conIdx] == constellationID or bidRange == const.rangeRegion):
    644                 found = 1
    645                 break
    646             elif askRange == const.rangeRegion and amount >= x[minIdx]:
    647                 found = 1
    648                 break
    649             i += 1
    650 
    651         if not found:
    652             return
    653         self.LogInfo('[GetBestBidInRange] found:', bids[i])
    654         return bids[i]
    655 
    656     def GetOrders(self, typeID):
    657         self.RefreshOrderCache(typeID)
    658         self.RefreshJumps(typeID)
    659         return [[self.orderCache[typeID][0], [['price', 1], ['jumps', 1]]], [self.orderCache[typeID][1], [['price', -1], ['jumps', 1]]]]
    660 
    661     def GetOrder(self, orderID, typeID = None):
    662         if typeID is not None:
    663             self.RefreshOrderCache(typeID)
    664             self.RefreshJumps(typeID)
    665         for asksAndBids in self.orderCache.values():
    666             asks, bids, stamp = asksAndBids
    667             for order in asks:
    668                 if order.orderID == orderID:
    669                     return order
    670 
    671             for order in bids:
    672                 if order.orderID == orderID:
    673                     return order
    674 
    675     def RefreshJumps(self, typeID, refreshOrders = 0, locationID = None):
    676         if refreshOrders:
    677             self.RefreshOrderCache(typeID)
    678         asks = self.orderCache[typeID][0]
    679         bids = self.orderCache[typeID][1]
    680         if locationID is None:
    681             getJumpCountFunc = self.clientPathfinderService.GetJumpCountFromCurrent
    682         else:
    683             getJumpCountFunc = functools.partial(self.clientPathfinderService.GetJumpCount, locationID)
    684         for row in itertools.chain(asks, bids):
    685             row.jumps = getJumpCountFunc(row.solarSystemID)
    686 
    687         asks.sort(lambda x, y, headers = asks.columns: SortAsks(x, y, headers))
    688         bids.sort(lambda x, y, headers = bids.columns: SortBids(x, y, headers))
    689 
    690     def ClearAll(self):
    691         self.orderCache = {}
    692 
    693     def RefreshAll(self):
    694         for typeID in self.orderCache.iterkeys():
    695             self.RefreshOrderCache(typeID)
    696             self.RefreshJumps(typeID)
    697 
    698     def RefreshOrderCache(self, typeID, forceUpdate = 0):
    699         self.LogInfo('[RefreshOrderCache] Refreshing', typeID)
    700         if typeID == typePlex:
    701             orders = self.GetMarketProxy().GetPlexOrders()
    702             version = sm.GetService('objectCaching').GetCachedMethodCallVersion(None, 'marketProxy', 'GetPlexOrders', ())
    703         else:
    704             orders = self.GetMarketProxy().GetOrders(typeID)
    705             version = sm.GetService('objectCaching').GetCachedMethodCallVersion(None, 'marketProxy', 'GetOrders', (typeID,))
    706         if typeID in self.orderCache:
    707             if self.orderCache[typeID][2] == version:
    708                 return
    709         asks, bids = orders[0], orders[1]
    710         self.orderCache[typeID] = [asks, bids, version]
    711         self.LogInfo('[RefreshOrderCache] Refresh done:', len(orders[0]), 'asks and', len(orders[1]), 'bids')
    712 
    713     @staticmethod
    714     def FactionAndCorpToCharStandings(marketLocationOwnerID, charID):
    715         if marketLocationOwnerID and idCheckers.IsNPC(marketLocationOwnerID):
    716             factionID = get_corporation_faction_id(marketLocationOwnerID)
    717             standingSvc = sm.GetService('standing')
    718             factionToCharStanding = standingSvc.GetStanding(factionID, charID)
    719             corpToCharStanding = standingSvc.GetStanding(marketLocationOwnerID, charID)
    720         else:
    721             factionToCharStanding, corpToCharStanding = (0.0, 0.0)
    722         return (factionToCharStanding, corpToCharStanding)
    723 
    724     def GetMaxAvailableAtPrice(self, typeID, stationID, price):
    725         try:
    726             buyItems, _ = sm.GetService('marketQuote').GetOrders(typeID)
    727             numAvailable = 0
    728             for order in buyItems[0]:
    729                 if order.typeID != typeID:
    730                     continue
    731                 if order.stationID != stationID:
    732                     continue
    733                 if order.price > price and not FloatCloseEnough(order.price, price):
    734                     continue
    735                 numAvailable += order.volRemaining
    736 
    737             return numAvailable
    738         except Exception:
    739             logger.exception('GetMaxAvailable')