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')